--- layout: default ---

3D line fit

In [1]:
import numpy as np
import matplotlib.pyplot as plt
import plotly
import plotly.offline as py
import plotly.graph_objs as go

py.init_notebook_mode()
config={'showLink': False, 'scrollZoom': True}
In [2]:
# Fixing random state for reproducibility
np.random.seed(1)

nbPoints=10
points = np.array( [5*np.arange(nbPoints),5+5*np.arange(nbPoints),5*np.ones(nbPoints)] ).T + 0.5-100*np.random.rand(nbPoints,3)
print(points)
[[-4.12022005e+01 -6.65324493e+01  5.48856252e+00]
 [-2.47332573e+01 -4.17558908e+00 -3.73385948e+00]
 [-8.12602114e+00 -1.90560727e+01 -3.41767474e+01]
 [-3.83816734e+01 -2.14194514e+01 -6.30219500e+01]
 [ 5.47750268e-02 -6.23117436e+01  2.76124068e+00]
 [-4.15467510e+01 -1.12304802e+01 -5.03689828e+01]
 [ 1.64613061e+01  1.56898511e+01 -7.45744569e+01]
 [-6.13261576e+01  9.15758218e+00 -6.37322616e+01]
 [-4.71389152e+01 -4.39606664e+01 -3.00442114e+00]
 [ 4.15945217e+01  3.35169580e+01 -8.23142503e+01]]
In [3]:
# Step 2: find the mean of the points
avg = np.mean(points, axis=0)

# Step 3: subtract the mean from all points
subtracted = points-avg

# Step 4 : perform SVD
_, _, V = np.linalg.svd(subtracted)

# Step 5: find the direction vector 
#        (which is the right singular vector corresponding to the largest singular value)
direction = V[0, :]

# Line is 'avg' and 'direction'
p0 = avg;
d = direction;
print(d)
[ 0.4063893   0.63608938 -0.65592533]
In [4]:
pa = p0 - 50*d
pb = p0 + 50*d

d0 = np.array([0,0,1])
na = p0 + 50*d0
nb = p0 - 50*d0
In [5]:
trace1 = go.Scatter3d(
    x=[pa[0],pb[0]],
    y=[pa[1],pb[1]],
    z=[pa[2],pb[2]],
    mode='lines',
    name='3D fitted line',
    line=go.Line(color='rgb(255,0,0)', width=10),
    hoverinfo='none')

trace3 = go.Scatter3d(
    x=[na[0],nb[0]],
    y=[na[1],nb[1]],
    z=[na[2],nb[2]],
    mode='lines',
    name='normal',
    line=go.Line(color='rgb(0,0,255)', width=10),
    hoverinfo='none')

labels = []
for i in range(nbPoints): labels += [str(i)]  
trace2 = go.Scatter3d(
    x=points[:,0],
    y=points[:,1],
    z=points[:,2],
    mode='markers',
    name='Points',
    marker=go.Marker(
        symbol='cross',
        opacity=1,
        color='rgb(0,200,127)'),
    text=labels,
    hoverinfo='text')

layout = go.Layout(
            title="3D line fitting",
            scene=go.Scene(
                    xaxis=go.XAxis(dict(title="x")),
                    yaxis=go.YAxis(dict(title="y")),
                    zaxis=go.ZAxis(dict(title="z")),
                    camera=dict(
                           up=dict(x=0, y=0, z=1),
                           center=dict(x=0, y=0, z=0),
                           eye=dict(x=0, y=2.5, z=0))))     


fig=go.Figure(data=go.Data([trace1, trace2, trace3]), layout=layout)
plotly.plotly.image.ishow(fig)
filedir=py.plot(fig, filename='../posts/figs/line.html', config=config, auto_open=False)
In [6]:
Angle = np.arccos(np.dot(d0,d)/(np.linalg.norm(d0) * np.linalg.norm(d)))
print(Angle*180/np.pi)
130.98985038070018